package jpsearch;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;

import org.apache.commons.lang3.StringUtils;
import org.jdom2.Attribute;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.input.SAXBuilder;
import org.jdom2.output.XMLOutputter;

import translation.ManyToOne;
import translation.ObjectFactory;
import translation.OneToMany;
import translation.OneToOne;
import translation.OneToOneFieldTranslationType;
import translation.RuleTypeConstants;
import translation.SourceFieldType;
import translation.SourceType;
import translation.TranslationRuleType;
import translation.TranslationRulesType;
import translation.VarBinding;

/*
 * This class holds the translated Elements stored within the Map structure.
 * It can read an XML file and get the elements or write the elements in Map
 * structure to a specific XML file.
 * The Rule Type Object is fully created/changed in the RuleController class.
 */

public class JPSearchRuleGenerator implements RuleTypeConstants {

	private Map<String, Object> translatedElements; //<XML element name, [OneToOne | OneToMany | ManyToOne]> 
	//private List<Element> xmlElements; Maybe used in recreating the XML file (adding elem, set attributes)

	public JPSearchRuleGenerator(){
		translatedElements = new LinkedHashMap<String, Object>();
	}

	//Get the rule based on the key saved
	public Object getRule(String key){
		return translatedElements.get(key);
	}

	public static void main(String[] args){

		/*//Reading from file
		File input = new File("test_intance.xml");
		for(String s : readXMLInstance(input)){
			System.out.println(s);
		}*/

		JPSearchRuleGenerator jpsGen = new JPSearchRuleGenerator();
		File output = new File("output_test.xml");

	}

	/**
	 * Add a Rule into the translated Elements map structure
	 * @param xPathKey
	 * @param o
	 */
	public void addToTranslatedElements(String xPathKey, Object o){
		translatedElements.put(xPathKey, o);
	}	

	/**
	 * Remove the rule based on key
	 * @param key
	 */
	public void removeRule(String key){
		translatedElements.remove(key);
	}

	/**
	 * Dumbs the translated elements to the system out
	 */
	public void dumbTranslatedElements(){
		Iterator<Map.Entry<String, Object>> it = translatedElements.entrySet().iterator();
		while (it.hasNext()) {	
			Map.Entry<String, Object> pair = it.next();
			if(pair.getValue() instanceof OneToOne){
				OneToOne o2o = (OneToOne)pair.getValue();
				System.out.println("OneToOne Key:" + pair.getKey() + " ToXPath" + o2o.getToXPath());
			} 
			else if(pair.getValue() instanceof OneToMany){
				OneToMany o2m = (OneToMany)pair.getValue();
				System.out.println("OneToMany Key:" + pair.getKey() + " ToFields" + o2m.getToFields());
			}
			else if(pair.getValue() instanceof ManyToOne){
				ManyToOne m2o = (ManyToOne)pair.getValue();
				System.out.println("ManyToOne Key:" + pair.getKey() + " ToXPath" + m2o.getToXPath());
			}
		}
		System.out.println();
	}

	/**
	 * Read an XML file and returns all elements in a list (as read, without duplicates)
	 * @param xmlInstance XML File to be read
	 * @return List of elements
	 */
	public List<String> readXMLInstance(File xmlInstance){
		SAXBuilder builder = new SAXBuilder();
		try {
			Document document = (Document) builder.build(xmlInstance);
			Element rootNode = document.getRootElement();
			List<Element> elements = rootNode.getChildren();
			Set<String> xmlElements = new LinkedHashSet<String>(); //to avoid duplicates (such as keyword)

			for(Element e : elements){
				//The map that contains the paths is structured as {xpath, value}. eg. {"author/name", "John"}
				//The map that contains the id of ROI type ONLY, is structures as {xpath, id}. eg. {"region_of_interest/description", 0}
				if(e.getChildren().size() >= 1){
					Map<String, String> paths = new LinkedHashMap<String, String>();
					Map<String,Integer> attibutes = new LinkedHashMap<String,Integer>();

					JPSearchTranslator.pathCreator(e, paths, attibutes);	
					Iterator<Map.Entry<String, String>> it = paths.entrySet().iterator();
					while (it.hasNext()) {	
						Map.Entry<String, String> pair = it.next();

						String xpath = pair.getKey();
						xmlElements.add(xpath);
					}
				}
				else{ //This way the parent elements of nested are avoided
					//Add current element to list
					xmlElements.add(e.getName());
				}
			}
			return new ArrayList<String>(xmlElements);
		}
		catch(Exception e){
			e.printStackTrace();
		}

		return null;
	}

	
	/**
	 * Writes all translated elements to an XML Rule file
	 * @param output The XML File to be written
	 */
	public void writeAllElementsToXMLFile(String fromFormat, File output){
		if(translatedElements.size() == 0) return;

		try {
			//Initialize output file
			if (!output.exists()) {
				output.createNewFile();
			}

			ObjectFactory of = new ObjectFactory();
			TranslationRulesType root = new TranslationRulesType();
			root.setFromFormat(fromFormat); 
			root.setToFormat("JPSearch:schema:coremetadata");

			Iterator<Map.Entry<String, Object>> it = translatedElements.entrySet().iterator();
			while (it.hasNext()) {	
				Map.Entry<String, Object> pair = it.next();
				Object o = translatedElements.get(pair.getKey());
				if(o instanceof OneToOne){
					OneToOne o2o = (OneToOne) o;
					root.getTranslationRule().add(o2o.toOneToOneFieldTranslationType());
				}
				else if(o instanceof OneToMany){
					OneToMany o2m = (OneToMany) o;
					root.getTranslationRule().add(o2m.toOneToManyFieldTranslationType());
				}
				else if(o instanceof ManyToOne){
					ManyToOne m2o = (ManyToOne)o;
					root.getTranslationRule().add(m2o.toManyToOneFieldTranslationType());
				}				
			}

			JAXBElement<TranslationRulesType> translationRules = of.createTranslationRules(root);

			//Marshalling process
			JAXBContext jaxbContext = JAXBContext.newInstance ("translation");
			Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
			jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);	
			jaxbMarshaller.marshal(translationRules, output);	

		}catch(JAXBException e){
			e.printStackTrace();
		}
		catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Writes all translated elements to an XML Rule file
	 * @param output The XML File to be written
	 */
	public String outputAllElementsToXMLFile(String fromFormat){
		if(translatedElements.size() == 0) return null;

		try {			
			ObjectFactory of = new ObjectFactory();
			TranslationRulesType root = new TranslationRulesType();
			root.setFromFormat(fromFormat); 
			root.setToFormat("JPSearch:schema:coremetadata");

			Iterator<Map.Entry<String, Object>> it = translatedElements.entrySet().iterator();
			while (it.hasNext()) {	
				Map.Entry<String, Object> pair = it.next();
				Object o = translatedElements.get(pair.getKey());
				if(o instanceof OneToOne){
					OneToOne o2o = (OneToOne) o;
					root.getTranslationRule().add(o2o.toOneToOneFieldTranslationType());
				}
				else if(o instanceof OneToMany){
					OneToMany o2m = (OneToMany) o;
					root.getTranslationRule().add(o2m.toOneToManyFieldTranslationType());
				}
				else if(o instanceof ManyToOne){
					ManyToOne m2o = (ManyToOne)o;
					root.getTranslationRule().add(m2o.toManyToOneFieldTranslationType());
				}				
			}

			JAXBElement<TranslationRulesType> translationRules = of.createTranslationRules(root);

			//Marshalling process
			JAXBContext jaxbContext = JAXBContext.newInstance ("translation");
			Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
			jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);	
			
			StringWriter sw = new StringWriter();
			jaxbMarshaller.marshal(translationRules, sw);	
			return sw.toString();

		}catch(JAXBException e){
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * An alternative method (not so correct) for writing to file.
	 * It uses the rule type's method for file writing
	 * @param output The file to write the XML
	 */
	public void alternativeWriteToXML(File output){
		if(translatedElements.size() == 0) return;

		BufferedWriter bw = null;

		try {
			FileWriter fw = new FileWriter(output.getAbsoluteFile()); //append to existing file
			bw = new BufferedWriter(fw);

			//write basic tag
			bw.write(xmlHeader + header);
			bw.close(); //so the header are written to the file

			Iterator<Map.Entry<String, Object>> it = translatedElements.entrySet().iterator();
			while (it.hasNext()) {	
				Map.Entry<String, Object> pair = it.next();
				Object o = translatedElements.get(pair.getKey());
				if(o instanceof OneToOne){
					((OneToOne) o).writeToXMLFile(output);
				}
				else if(o instanceof OneToMany){
					((OneToMany) o).writeToXMLFile(output);
				}
				else if(o instanceof ManyToOne){
					((ManyToOne)o).writeToXMLFile(output);
				}				
			}

			//re-initiate buffered writer
			fw = new FileWriter(output.getAbsoluteFile(),true);
			bw = new BufferedWriter(fw); 
			bw.write(footer);

		}catch (IOException e) {
			e.printStackTrace();
		}
		finally{
			try {
				bw.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}
